package com.xing.netty;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class NioServer {

    final SelectorProvider selectorProvider = SelectorProvider.provider();

    Selector bossSelector;
    Selector readWriteSelector;

    ByteBuffer readBuffer = ByteBuffer.allocate(512);
    ByteBuffer sendBuffer = ByteBuffer.allocate(512);

    String data;

    Runnable acceptEventLoop = () -> {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                bossSelector.select();
                Set<SelectionKey> keys = bossSelector.selectedKeys();
                Iterator<SelectionKey> keyIterator = keys.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    if (!key.isValid()) {
                        continue;
                    }
                    if (key.isAcceptable()) {
                        System.out.println("触发accept事件....");
                        accept(key);
                    }
                    keyIterator.remove(); //该事件已经处理需要丢弃
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    };

    Runnable readWriteEventLoop = () -> {
        for (; ; ) {
            try {
                readWriteSelector.select();
                Iterator<SelectionKey> keyIterator = readWriteSelector.selectedKeys().iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();
                    if (key.isReadable()) {
                        System.out.println("触发read事件....");
                        read(key);
                    } else if (key.isWritable()) {
                        System.out.println("触发write事件....");
                        write(key);
                    }
                    keyIterator.remove();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    };

    Runnable wakeupTask = () -> {
        for (; ; ) {
            readWriteSelector.wakeup();
            try {
                TimeUnit.SECONDS.sleep(5);
                System.out.println("唤醒读写selector.....");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

    public void start() throws Exception {
        bossSelector = selectorProvider.openSelector();
        readWriteSelector = selectorProvider.openSelector();

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress("localhost", 8888));
        ssc.register(bossSelector, SelectionKey.OP_ACCEPT);

        new Thread(readWriteEventLoop).start();
        new Thread(acceptEventLoop).start();
        new Thread(wakeupTask).start();
    }

    private void accept(SelectionKey key) throws Exception {
        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = ssc.accept();
        clientChannel.configureBlocking(false);

        clientChannel.register(readWriteSelector, SelectionKey.OP_READ);
        System.out.println("a new client connected " + clientChannel.getRemoteAddress());
    }

    private void write(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();
        System.out.println("write: " + data);

        sendBuffer.clear();
        sendBuffer.put(data.getBytes());
        sendBuffer.flip();

        channel.write(sendBuffer);
        channel.register(readWriteSelector, SelectionKey.OP_READ);
    }

    private void read(SelectionKey key) throws Exception {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        this.readBuffer.clear();
        int numRead;
        try {
            numRead = socketChannel.read(this.readBuffer);
        } catch (IOException e) {
            key.cancel();
            socketChannel.close();
            return;
        }
        if (numRead == -1) {
            key.channel();
            socketChannel.close();
            return;
        }

        data = new String(readBuffer.array(), 0, numRead);
        System.out.println("接收到信息: " + data);
        socketChannel.register(readWriteSelector, SelectionKey.OP_WRITE);
    }

    public static void main(String[] args) throws Exception {
        System.out.println("server started...");
        new NioServer().start();
    }

}
